package org.springframework.web.method.support;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.util.StringUtils;
import org.springframework.web.bin.annotation.JsonField;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;

public class JsonMethodArgumentResolver implements HandlerMethodArgumentResolver {
    private final ParameterNameDiscoverer parameterDiscoverer = new LocalVariableTableParameterNameDiscoverer();    
    
    private NativeWebRequest webRequest;
    private String requestBody;    
    
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(JsonField.class) && 
                (parameter.getParameterType().isPrimitive() 
                  || parameter.getParameterType().equals(String.class) 
                  || parameter.getParameterType().equals(Date.class));
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
            throws Exception {        
        parameter.initParameterNameDiscovery(parameterDiscoverer);
        
        handleRequest(webRequest);
        JsonNode node = parseJson(requestBody);       
        String jsonKey = getJsonKey(parameter);
        return resolveArgumentImpl(node, jsonKey, parameter);        
    }    
    
    private Object resolveArgumentImpl(JsonNode node, String key, MethodParameter param) {
        String value = node.path(key).asText(); 
        return new GenericConversionService().convert(value, param.getParameterType());
    }
    
    private void handleRequest(NativeWebRequest webRequest) throws IOException {
        if (this.webRequest != webRequest)
            this.requestBody = getRequestHttpBody(webRequest);
        this.webRequest = webRequest; 
    }
    
    private String getRequestHttpBody(NativeWebRequest webRequest) throws IOException {
        HttpServletRequest nativeRequest = webRequest.getNativeRequest(HttpServletRequest.class);        
        BufferedReader reader = new BufferedReader(new InputStreamReader(nativeRequest.getInputStream(), Charset.forName("UTF-8")));
        StringBuilder stringBuilder = new StringBuilder();
        
        String line;
        while((line = reader.readLine()) != null) {
            stringBuilder.append(line);
        }
        
        return stringBuilder.toString();
    }    
    
    private JsonNode parseJson(String jsonStr) throws JsonProcessingException, IOException {        
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readTree(jsonStr);        
    }
    
    private String getJsonKey(MethodParameter parameter) {
        JsonField annotation = parameter.getParameterAnnotation(JsonField.class);
        return StringUtils.hasText(annotation.value()) ? annotation.value() : parameter.getParameterName();
    }

}
